from datetime import datetime
import os
import json
import re
from django.conf import settings
import pytz
import requests
import logging
from apps.chefs.models import DjChef
from apps.address.models import Address
logger = logging.getLogger(__name__)

def get_wechat_config(app_type='consumer'):
    """
    获取微信配置
    :param app_type: 'consumer' 或 'provider'
    :return: 微信配置字典
    """
    return {
        'consumer': {
            'appid': os.environ['WECHAT_CONSUMER_APPID'],
            'secret': os.environ['WECHAT_CONSUMER_SECRET'],
        },
        'provider': {
            'appid': os.environ.get('WECHAT_PROVIDER_APPID', ''),
            'secret': os.environ.get('WECHAT_PROVIDER_SECRET', ''),
            'WECHAT_PROVIDER_ORDER_TEMPLATE_ID': os.environ['WECHAT_PROVIDER_ORDER_TEMPLATE_ID']
        }
    }.get(app_type, {})


def send_provider_notification(chef, order):
    """
    向厨师发送新订单微信订阅通知（严格匹配模板）
    功能：1. 兼容chef参数（DjChef对象/ID）；2. 通过address_id关联获取地址；3. 完整参数校验
    模板参数：
    - character_string5: 预约单号
    - time11: 预约时间
    - thing6: 服务地点（通过address_id关联Address模型获取）
    """
    logger.info(f"=== 开始处理订单[{order.id}]的厨师通知发送 ===")
    
    # --------------------------
    # 1. 处理chef参数（兼容对象/ID）
    # --------------------------
    try:
        if isinstance(chef, DjChef):
            dj_chef = chef
            logger.debug(f"传入DjChef对象，直接使用（厨师ID: {dj_chef.id}）")
        elif isinstance(chef, (int, str)) and str(chef).isdigit():
            chef_id = int(chef)
            dj_chef = DjChef.objects.get(id=chef_id)
            logger.debug(f"传入chef_id[{chef_id}]，查询到DjChef对象（ID: {dj_chef.id}）")
        else:
            logger.error(f"无效的chef参数：类型={type(chef)}，值={chef}")
            return False
    except DjChef.DoesNotExist:
        logger.error(f"DjChef模型中未找到ID为[{chef}]的记录")
        return False
    except Exception as e:
        logger.error(f"解析chef参数异常：{str(e)}", exc_info=True)
        return False

    # --------------------------
    # 2. 校验厨师订阅状态和openid（仅依赖DjChef模型）
    # --------------------------
    if not dj_chef.subscribe_status:
        logger.warning(f"厨师[{dj_chef.id}]未订阅通知（subscribe_status=False）")
        return False
    if not hasattr(dj_chef, "openid") or not dj_chef.openid:
        logger.error(f"厨师[{dj_chef.id}]的openid缺失（值: {getattr(dj_chef, 'openid', None)}）")
        return False
    logger.info(f"厨师[{dj_chef.id}]基础校验通过，openid: {dj_chef.openid[:6]}***")

    # --------------------------
    # 3. 校验微信通知配置（环境变量+provider配置）
    # --------------------------
    try:
        # 获取provider小程序配置
        provider_config = get_wechat_config("provider")
        template_id = os.environ.get("WECHAT_PROVIDER_ORDER_TEMPLATE_ID", "").strip()

        # 检查必要配置项
        missing_configs = []
        if not provider_config.get("appid"):
            missing_configs.append("WECHAT_PROVIDER_APPID（provider_config.appid）")
        if not provider_config.get("secret"):
            missing_configs.append("WECHAT_PROVIDER_SECRET（provider_config.secret）")
        if not template_id:
            missing_configs.append("WECHAT_PROVIDER_ORDER_TEMPLATE_ID（环境变量）")

        if missing_configs:
            logger.error(f"微信配置缺失：{', '.join(missing_configs)}")
            return False
        logger.debug(f"微信配置完整，模板ID: {template_id[:6]}***")
    except Exception as e:
        logger.error(f"读取微信配置异常：{str(e)}", exc_info=True)
        return False

    # --------------------------
    # 4. 获取微信access_token（provider类型）
    # --------------------------
    access_token = get_provider_access_token()
    if not access_token:
        logger.error("获取provider access_token失败，终止通知发送")
        return False
    logger.debug(f"获取access_token成功：{access_token[:10]}***")

    # --------------------------
    # 5. 构造模板参数（严格匹配微信模板）
    # --------------------------
    try:
        # 5.1 预约单号（character_string5）- 非空
        order_number = getattr(order, "order_number", "").strip() if hasattr(order, "order_number") else ""
        if not order_number:
            order_number = f"ORD{order.id}"  # 兜底：用订单ID生成临时单号
            logger.warning(f"订单[{order.id}]order_number为空，使用临时单号: {order_number}")

        # 5.2 预约时间（time11）- 格式：YYYY-MM-DD HH:MM
        shanghai_tz = pytz.timezone(settings.TIME_ZONE)  # 转换为tzinfo实例

        # 处理booked_time：优先使用订单时间，异常时用当前时间兜底
        if hasattr(order, "booked_time") and order.booked_time:
            try:
                # 情况1：booked_time是带有效时区的datetime对象
                if isinstance(order.booked_time, datetime):
                    if order.booked_time.tzinfo is not None:
                        # 转换为上海时区
                        shanghai_time = order.booked_time.astimezone(shanghai_tz)
                    else:
                        # 无时区信息时，强制标记为上海时区（兼容历史数据）
                        shanghai_time = shanghai_tz.localize(order.booked_time, is_dst=None)
                    
                    booked_time = shanghai_time.strftime("%Y-%m-%d %H:%M")
                    logger.debug(f"订单[{order.id}]booked_time转换完成: {booked_time}")
                
                # 情况2：booked_time是字符串（异常场景处理）
                else:
                    # 尝试解析字符串为UTC时间后转换
                    parsed_utc = datetime.fromisoformat(str(order.booked_time)).replace(tzinfo=pytz.UTC)
                    shanghai_time = parsed_utc.astimezone(shanghai_tz)
                    booked_time = shanghai_time.strftime("%Y-%m-%d %H:%M")

            except Exception as e:
                logger.warning(f"订单[{order.id}]booked_time格式错误，用当前时间替代: {str(e)}")
                # 异常时使用当前上海时间
                booked_time = datetime.now(shanghai_tz).strftime("%Y-%m-%d %H:%M")
        else:
            # 订单无booked_time时，直接用当前时间
            booked_time = datetime.now(shanghai_tz).strftime("%Y-%m-%d %H:%M")

        # 5.3 服务地点（thing6）- 通过address_id关联Address模型
        service_location = "未知地点"  # 兜底值
        if hasattr(order, "address_id") and order.address_id:
            try:
                address_id = int(order.address_id)
                # 查询address应用的Address模型
                address_obj = Address.objects.get(id=address_id)
                # 提取address_detail字段（确保非空）
                if hasattr(address_obj, "address_detail"):
                    address_detail = str(address_obj.address_detail).strip()
                    if address_detail:
                        service_location = address_detail[:20]  # 限制thing类型最大长度20字
                        logger.debug(f"订单[{order.id}]获取地址成功：{service_location[:15]}***")
            except Address.DoesNotExist:
                logger.warning(f"Address模型中未找到ID为[{order.address_id}]的记录")
            except Exception as e:
                logger.error(f"订单[{order.id}]查询地址异常：{str(e)}")
        else:
            logger.warning(f"订单[{order.id}]address_id缺失或无效")

        # 5.4 最终模板参数（无多余字段，无空值）
        template_data = {
            "character_string5": {"value": order_number},
            "time11": {"value": booked_time},
            "thing6": {"value": service_location}
        }
        logger.info(f"构造模板参数：{json.dumps(template_data, ensure_ascii=False)}")

    except Exception as e:
        logger.error(f"构造模板参数异常：{str(e)}", exc_info=True)
        return False

    # --------------------------
    # 6. 发送微信订阅消息请求
    # --------------------------
    try:
        # 构造请求体
        request_data = {
            "touser": dj_chef.openid,
            "template_id": template_id,
            "page": f"/pages/order/detail?id={order.id}",  # 点击通知跳转的小程序页面
            "data": template_data
        }

        # 发送POST请求
        api_url = f"https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={access_token}"
        logger.debug(f"发送请求到微信接口：{api_url[:50]}***")
        response = requests.post(
            api_url,
            json=request_data,
            timeout=15,  # 延长超时时间，避免网络波动导致失败
            headers={"Content-Type": "application/json"}
        )
        response.raise_for_status()  # 触发HTTP状态码错误（如404/500）
        response_data = response.json()
        logger.info(f"微信接口响应：{json.dumps(response_data, ensure_ascii=False)}")

        # 解析响应结果
        if response_data.get("errcode") == 0:
            logger.info(f"=== 订单[{order.id}]通知发送成功（厨师[{dj_chef.id}]）===")
            return True
        else:
            # 错误码映射（便于快速定位）
            error_map = {
                40037: "模板ID错误或已失效",
                40003: "openid无效（未绑定或已注销）",
                47003: "参数错误（空值/类型不匹配/关键词错误）",
                41030: "page路径错误（小程序页面不存在）",
                41014: "access_token无效或过期",
                43101: "用户拒绝接收该模板消息"
            }
            error_msg = error_map.get(response_data["errcode"], response_data["errmsg"])
            logger.error(f"=== 订单[{order.id}]通知发送失败：错误码={response_data['errcode']}，原因={error_msg} ===")
            return False

    except requests.exceptions.RequestException as e:
        logger.error(f"微信接口请求异常：{str(e)}", exc_info=True)
        return False
    except Exception as e:
        logger.error(f"处理微信响应异常：{str(e)}", exc_info=True)
        return False


def get_provider_access_token():
    """
    获取provider小程序的微信access_token（独立工具函数）
    依赖：get_wechat_config('provider')返回appid和secret
    """
    logger.debug("=== 开始获取provider access_token ===")
    try:
        # 获取配置
        provider_config = get_wechat_config("provider")
        appid = provider_config.get("appid", "").strip()
        secret = provider_config.get("secret", "").strip()

        # 校验配置
        if not appid or not secret:
            logger.error(f"provider配置缺失：appid={appid}, secret={secret[:3]}***")
            return None

        # 调用微信接口
        api_url = (
            f"https://api.weixin.qq.com/cgi-bin/token"
            f"?grant_type=client_credential"
            f"&appid={appid}"
            f"&secret={secret}"
        )
        response = requests.get(api_url, timeout=10)
        response.raise_for_status()
        result = response.json()

        # 解析结果
        if "access_token" in result:
            logger.debug(f"获取access_token成功（有效期：{result.get('expires_in', 7200)}秒）")
            return result["access_token"]
        else:
            logger.error(f"获取access_token失败：{json.dumps(result, ensure_ascii=False)}")
            return None

    except Exception as e:
        logger.error(f"获取provider access_token异常：{str(e)}", exc_info=True)
        return None